package com.rengwuxian.coursecoroutines._1_basics

import android.os.Bundle
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.rengwuxian.coursecoroutines.R
import com.rengwuxian.coursecoroutines.common.Contributor
import com.rengwuxian.coursecoroutines.common.gitHub
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

/**
 * 连接线程世界：和回调型API协作
 * 回调、suspendCoroutine()、suspendCancellableCoroutine
 *
 * 在suspendCoroutine()中放入传统回调写法的API调用，就能变成suspend函数，在挂起函数中启动该传统API，
 * 并改造onResponse和onFailure称为Continuation回调后，就可以在挂起函数中返回结果。
 *  private suspend fun callbackToSuspend() = suspendCoroutine {
 *     gitHub.contributorsCall("square", "retrofit")
 *       .enqueue(object : Callback<List<Contributor>> {
 *         override fun onResponse(
 *           call: Call<List<Contributor>>, response: Response<List<Contributor>>,
 *         ) {
 *           it.resume(response.body()!!)
 *         }
 *
 *         override fun onFailure(call: Call<List<Contributor>>, t: Throwable) {
 *           it.resumeWithException(t)
 *         }
 *       })
 *   }
 *  返回值就是resume里面的response.body()
 *  和前面串行使用suspend拿网络回调结果的写法一样，下一行就可以拿结果刷新界面。
 *  异常的捕获可以这么写：
 *     val job = lifecycleScope.launch {
 *       try {
 *         val contributors = callbackToSuspend()
 *         showContributors(contributors)
 *       } catch (e: Exception) {
 *         infoTextView.text = e.message
 *       }
 *     }
 *  try...catch不能内部包着launch, 这样只会捕获协程启动的异常，不会捕获协程中挂起函数的异常，因为是异步的。
 *
 *  suspendCancellableCoroutine和suspendCoroutine的唯一区别，就是支持取消。job.cancel()
 *  协程的取消，并不仅仅是cancel，而是协程内部的挂起函数支持被cancel的特性才行，否则是无法取消的。
 *  比如经典例子：
 *  delay挂起函数支持取消，只打印第一行日志。
 *     val job = lifecycleScope.launch {
 *       println("Coroutine cancel: 1")
 *       delay(500)
 *       println("Coroutine cancel: 2")
 *     }
 *
 *     lifecycleScope.launch {
 *       delay(200)
 *       job.cancel()
 *     }
 *  sleep不支持该特性，两行日志都打印
 *     val job = lifecycleScope.launch {
 *       println("Coroutine cancel: 1")
 *       Thread.sleep(500)
 *       println("Coroutine cancel: 2")
 *     }
 *
 *     lifecycleScope.launch {
 *       delay(200)
 *       job.cancel()
 *     }
 *  sleep这个例子，我对扔物线的说法是存疑的，因为cancel的方法在500ms内并没有被执行，因为sleep是占用主线程的，不像delay是让出当前线程的特性。
 *  结果没有问题，只是觉得说法有问题。
 *  证明如下：（cancel都没有走到）
 *  2025-05-12 22:15:45.928  9957-9957  System.out              com.rengwuxian.coursecoroutines      I  Coroutine cancel: 1
 *  2025-05-12 22:15:46.428  9957-9957  System.out              com.rengwuxian.coursecoroutines      I  Coroutine cancel: 2
 *  2025-05-12 22:15:46.428  9957-9957  System.out              com.rengwuxian.coursecoroutines      I  Coroutine cancel: 3
 *
 *  同理，suspendCoroutine就是不会配合协程取消，而停止运行。只有suspendCancellableCoroutine才支持。
 *  一般都用suspendCancellableCoroutine就行。通用。
 *  但是也别忘记suspendCoroutine不能取消执行的特性。
 *
 * suspendCancellableCoroutine被取消的收尾工作可以使用CancellableContinuation的回调invokeOnCancellation来实现。
 *   private suspend fun callbackToCancellableSuspend() = suspendCancellableCoroutine {
 *     it.invokeOnCancellation {
 *      // 协程被取消的时候才会执行，一般不会回调的
 *     }
 *     println("callbackToCancellableSuspend.....")
 *     gitHub.contributorsCall("square", "retrofit")
 *       .enqueue(object : Callback<List<Contributor>> {
 *         override fun onResponse(
 *           call: Call<List<Contributor>>, response: Response<List<Contributor>>,
 *         ) {
 *           it.resume(response.body()!!)
 *         }
 *
 *         override fun onFailure(call: Call<List<Contributor>>, t: Throwable) {
 *           it.resumeWithException(t)
 *         }
 *       })
 *   }
 */
class CallbackToSuspendActivity : ComponentActivity() {
  private lateinit var infoTextView: TextView

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.layout_1)
    infoTextView = findViewById(R.id.infoTextView)

    val job1 = lifecycleScope.launch {
      try {
        val contributors = callbackToCancellableSuspend()
        showContributors(contributors)
      } catch (e: Exception) {
        infoTextView.text = e.message
      }
    }
//
//    lifecycleScope.launch {
//      suspendCancellableCoroutine {
//
//      }
//    }

    val job = lifecycleScope.launch {
      println("Coroutine cancel: 1")
      Thread.sleep(500)
      println("Coroutine cancel: 2")
    }

    lifecycleScope.launch {
      println("Coroutine cancel: 3")
      delay(200)
      job.cancel()
    }
  }

  private suspend fun callbackToSuspend() = suspendCoroutine {
    gitHub.contributorsCall("square", "retrofit")
      .enqueue(object : Callback<List<Contributor>> {
        override fun onResponse(
          call: Call<List<Contributor>>, response: Response<List<Contributor>>,
        ) {
          it.resume(response.body()!!)
        }

        override fun onFailure(call: Call<List<Contributor>>, t: Throwable) {
          it.resumeWithException(t)
        }
      })
  }

  private suspend fun callbackToCancellableSuspend() = suspendCancellableCoroutine {
    it.invokeOnCancellation {

    }
    println("callbackToCancellableSuspend.....")
    gitHub.contributorsCall("square", "retrofit")
      .enqueue(object : Callback<List<Contributor>> {
        override fun onResponse(
          call: Call<List<Contributor>>, response: Response<List<Contributor>>,
        ) {
          it.resume(response.body()!!)
        }

        override fun onFailure(call: Call<List<Contributor>>, t: Throwable) {
          it.resumeWithException(t)
        }
      })
  }

  private fun callbackStyle() {
    gitHub.contributorsCall("square", "retrofit")
      .enqueue(object : Callback<List<Contributor>> {
        override fun onResponse(
          call: Call<List<Contributor>>, response: Response<List<Contributor>>,
        ) {
          showContributors(response.body()!!)
        }

        override fun onFailure(call: Call<List<Contributor>>, t: Throwable) {
          t.printStackTrace()
        }
      })
  }

  private fun showContributors(contributors: List<Contributor>) = contributors
    .map { "${it.login} (${it.contributions})" }
    .reduce { acc, s -> "$acc\n$s" }
    .let { infoTextView.text = it }
}